const sha256 = require('crypto-js/sha256');
const elliptic = require('elliptic').ec;
const ec = new elliptic('secp256k1');

class Transaction {
    constructor(from, to, amount) {
        this.from = from;
        this.to = to;
        this.amount = amount;
    }
    computeHash() {
        return sha256(this.from+this.to+this.amount).toString();
    }
    sign(key) {
        this.signature = key.sign(this.computeHash(), 'base64').toDER('hex');
    }
    verifySignature() {
        if (this.from==='minerReward') {
            return true;
        }
        let keyobj = ec.keyFromPublic(this.from, 'hex');
        return keyobj.verify(this.computeHash(), this.signature);
    }
}

class Block {
    constructor(transactions, previousHash=null) {
        this.transactions = transactions;//比特币的一个区块大小有限，区块中的交易记录也有限
        this.previousHash = previousHash;
        this.num = 0;
        this.hash = "";
        this.timestamp = Date.now();
    }
    computeHash() {
        return sha256(JSON.stringify(this.transactions)+this.previousHash+this.num+this.timestamp).toString();
    }
    zeroStr(difficulty) {
        let str = "";
        for (let i = 0; i < difficulty; i++) {
            str += "0";
        }
        return str;
    }
    mining(difficulty) {
        if (!this.validdataAllTransactions()) {
            return;
        }
        do {
            this.num++;
            this.hash = this.computeHash();
        } while (this.hash.substring(0,difficulty)!==this.zeroStr(difficulty));
        console.log("挖矿结束", this.hash);
    }
    validdataAllTransactions()
    {
        for (let i = 0; i < this.transactions.length; i++) {
            if (!this.transactions[i].verifySignature()) {
                console.log("发现交易签名错误");
                return false;
            }
        }
        return true;
    }
}

class Chain {
    constructor() {
        this.difficulty = 5;
        this.chain = [];
        this.transactionPool = [];
        this.minerReward = 50;
        this._init();
    }
    _init() {
        this.chain.push(new Block("first block", "first block"));
    }
    _getLastBlock() {
        if (this.chain.length) {
            return this.chain[this.chain.length-1];
        } else {
            return null;
        }
    }
    //添加块到链上
    // addBlock(block) {
    //     block.previousHash = this._getLastBlock().hash;
    //     block.mining(this.difficulty);
    //     this.chain.push(block);
    // }
    //添加交易记录
    addTransaction(transaction) {
        if (transaction.verifySignature()) {
            this.transactionPool.push(transaction);
        } else {
            console.log("交易签名验证错误");
        }
    }
    //挖矿过程
    mineTransaction(minerAddress) {
        //奖励
        let reward = new Transaction('minerReward', minerAddress, this.minerReward);
        this.transactionPool.push(reward);
        //挖矿后添加块到链上
        let block = new Block(this.transactionPool, this._getLastBlock().hash);
        block.mining(this.difficulty);
        this.chain.push(block);
        this.transactionPool = [];//比特币的交易池很大，实际不会一次挖所有的交易
    }
    //验证整个链
    checkChain() {
        if (this.chain.length===1) {
            return this.chain[0].hash===this.chain[0].computeHash();
        } else {
            for (let i = 1; i < this.chain.length; i++) {
                const element = this.chain[i];
                if (!this.chain[i].validdataAllTransactions()) {
                    console.log(`区块${i}交易异常`);
                    return false;
                }
                if (element.hash!==element.computeHash()) {
                    console.log(`区块${i}异常`);
                    return false;
                }
                const previous = this.chain[i-1];
                if (element.previousHash!==previous.hash) {
                    console.log(`区块${i}与前一个区块断开`);
                    return false;
                }
            }
            return true;
        }
    }
}

class User {
    constructor(name){
        this.name = name;
        this.keyPair = ec.genKeyPair();
        this.privateKey = this.keyPair.getPrivate('hex');
        this.publicKey = this.keyPair.getPublic('hex');
    }
}

let user1 = new User("account1");
let user2 = new User("account2");
let user3 = new User("account3");
let myCoin = new Chain();
let t1 = new Transaction(user1.publicKey, user2.publicKey, 155);
t1.sign(user1.keyPair);
let t2 = new Transaction(user2.publicKey, user1.publicKey, 166);
t2.sign(user2.keyPair);
// t1.amount = 255;
myCoin.addTransaction(t1);
myCoin.addTransaction(t2);
myCoin.mineTransaction(user3.publicKey);
myCoin.checkChain();
console.log(myCoin);
console.log(myCoin.chain[1].transactions);